package com.peas.platform.common.core.utils;

import com.peas.platform.common.core.constant.Constants;
import lombok.SneakyThrows;
import org.springframework.lang.NonNull;

import java.io.File;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author caizhh
 */
public class ClazzUtil {

    private static void addClasses(Class<?> clazz, List<Class<?>> classes, Class<?> parent) {
        // 如果parent是interface
        if (checkExtendsImplement(clazz, parent)) {
            classes.add(clazz);
        }
    }

    public static boolean checkExtendsImplement(Class<?> sub, Class<?> parent) {
        if (parent == null || parent == Object.class) {
            return true;
        }
        if (parent.isMemberClass() || parent.isAnonymousClass() || parent.isLocalClass()) {
            if (sub.getSuperclass() != parent) {
                return false;
            }
        }
        if (parent.isInterface()) {
            Class<?>[] interfaces = sub.getInterfaces();
            for (Class<?> inter : interfaces) {
                if (inter == parent) {
                    return true;
                }
            }
        }
        return sub.getSuperclass() == parent;
    }

    @SneakyThrows
    private static void findAndAddClassesInPackageByJar(@NonNull URL url, @NonNull String packageDirName, @NonNull List<Class<?>> classes, Class<?> parentClass) {
        String packageName = Constants.BASE_PACKAGE;
        JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String name = entry.getName();
            if (name.charAt(0) == '/') {
                name = name.substring(1);
            }
            if (name.startsWith(packageDirName)) {
                int idx = name.lastIndexOf('/');
                if (idx != -1) {
                    packageName = name.substring(0, idx).replace('/', '.');
                }
                if (idx != -1) {
                    if (name.endsWith(".class") && !entry.isDirectory()) {
                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                        addClasses(Class.forName(packageName + '.' + className), classes, parentClass);
                    }
                }
            }
        }
    }

    @SneakyThrows
    private static <T> void findAndAddClassesInPackageByFile(@NonNull String packageName, @NonNull String packagePath, @NonNull List<Class<?>> classes, Class<?> parent) {
        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        File[] dirFiles = dir.listFiles();
        if (dirFiles != null) {
            for (File file : dirFiles) {
                if (file.isDirectory()) {
                    findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes, parent);
                } else {
                    String className = file.getName();
                    if (className.endsWith(".class")) {
                        className = className.substring(0, className.length() - 6);
                        addClasses(Class.forName(packageName + '.' + className), classes, parent);
                    }
                }
            }
        }
    }

    @SneakyThrows
    public static <T> List<Class<?>> getAllSubClass(Class<?> parentClass) {
        List<Class<?>> classes = new ArrayList<>();
        String packageDirName = Constants.BASE_PACKAGE.replace('.', '/');
        Enumeration<URL> dirs;
        dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();
            String protocol = url.getProtocol();
            if ("file".equals(protocol)) {
                String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8.name());
                findAndAddClassesInPackageByFile(Constants.BASE_PACKAGE, filePath, classes, parentClass);
            }
            if ("jar".equals(protocol)) {
                findAndAddClassesInPackageByJar(url, packageDirName, classes, parentClass);
            }
        }
        return classes;
    }

}
